package se

import (
	"io/fs"
	"os"
	"path/filepath"

	"gitee.com/xuender/oils/level"
	"gitee.com/xuender/oils/logs"
	"gitee.com/xuender/oils/sets"
	"gitee.com/xuender/oils/slices"
	"gitee.com/xuender/oils/tags"
	"gitee.com/xuender/oils/u32"
	"gitee.com/xuender/se/pb"
	"github.com/syndtr/goleveldb/leveldb/util"
)

// FileService 文件服务.
type FileService struct {
	ctx            *Context
	ls             *level.Service
	fileIDSeq      [2]byte // 文件ID序列
	fileIDPrefix   [2]byte // 文件ID对应文件信息
	fileWordPrefix [2]byte // 文件包含的词
	fileIDDelKey   [2]byte // 删除的文件ID
	fileIDKey      [2]byte // 文件名对饮文件ID
	wordFilePrefix [2]byte // 词包含的文件
}

// NewFileService 新建文件服务.
func NewFileService(
	ctx *Context,
	ls *level.Service,
) *FileService {
	return &FileService{
		ctx:            ctx,
		ls:             ls,
		fileIDSeq:      [2]byte{ByteSeq, ByteFile},
		fileIDPrefix:   [2]byte{ByteFile, BytePrefix},
		fileWordPrefix: [2]byte{ByteFile, ByteWord},
		fileIDKey:      [2]byte{ByteFile, ByteFile},
		fileIDDelKey:   [2]byte{ByteFile, ByteID},
		wordFilePrefix: [2]byte{ByteWordFile, BytePrefix},
	}
}

// File 生成文件信息.
func (p *FileService) File(file string) (*pb.File, bool) {
	info, err := os.Stat(file)
	if os.IsNotExist(err) {
		logs.Debug("文件不存在:", file)
		p.Delete(file)

		return nil, false
	}

	logs.Panic(err)

	return p.file(file, info)
}

// DeleteDir 删除目录.
func (p *FileService) DeleteDir(dir string) {
	p.Delete(dir)
	_ = filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
		if info.IsDir() {
			return nil
		}

		p.Delete(path)

		return nil
	})
}

// Delete 删除文件.
func (p *FileService) Delete(file string) {
	logs.Debug("删除", file)

	fileKey := p.fileIDKey[:]
	fileKey = append(fileKey, []byte(file)...)
	fileIDKey := []byte{}

	if !p.ls.Get(fileKey, &fileIDKey) {
		return
	}

	of := &pb.File{}
	if !p.ls.Get(fileIDKey, of) {
		return
	}

	if of.IsDir {
		// 删除索引目录
		key := []byte{ByteFile, ByteIndex}
		is := []string{}

		if p.ls.Get(key, &is) {
			ss := slices.Strings(is)

			ss.DelAll(of.Name)
			p.ls.Put(key, []string(ss))
		}
	} else {
		p.Tag(of.Id, tags.NewTags())
	}

	p.ls.Push(p.fileIDDelKey[:], of.Id)

	logs.Panic(p.ls.DB.Delete(fileIDKey, nil))
	logs.Panic(p.ls.DB.Delete(fileKey, nil))
}

// 根据FileInfo创建pb.File，并判断是否有修改.
func (p *FileService) file(file string, info os.FileInfo) (*pb.File, bool) {
	nf := &pb.File{
		Name:    file,
		IsDir:   info.IsDir(),
		ModTime: info.ModTime().UnixNano(),
		Size:    info.Size(),
	}

	fileKey := p.fileIDKey[:]
	fileKey = append(fileKey, []byte(file)...)
	of := &pb.File{}
	fileIDKey := []byte{}

	if p.ls.Get(fileKey, &fileIDKey) {
		p.ls.Get(fileIDKey, of)
		logs.Debug("文件已经索引:", file)

		nf.Id = of.Id

		p.ls.Put(fileIDKey, nf)

		return nf, of.IsDir || of.Size != nf.Size || of.ModTime != nf.ModTime || of.IsDir != nf.IsDir
	}

	id := uint32(0)
	if i := p.ls.Pop(p.fileIDDelKey[:], &id); i == 0 {
		id = p.ls.NextSequence(p.fileIDSeq[:])
	}

	fileIDKey = p.fileIDPrefix[:]
	fileIDKey = append(fileIDKey, u32.Bytes(id)...)
	nf.Id = id

	logs.Info("索引文件:", file)

	p.ls.Put(fileKey, fileIDKey)
	p.ls.Put(fileIDKey, nf)

	return nf, true
}

// Tag 给文件打标签.
func (p *FileService) Tag(fid uint32, ts tags.Tags) {
	fk := p.fileWordPrefix[:]
	fk = append(fk, u32.Bytes(fid)...)
	ots := tags.NewTags()
	// 文件包含的词历史
	if p.ls.Get(fk, &ots) {
		ots.DelTags(ts)
		// 删除词关联的文件
		for _, wid := range ots.Slice() {
			wt := tags.NewTags()
			wk := p.wordFilePrefix[:]
			wk = append(wk, u32.Bytes(wid)...)

			if p.ls.Get(wk, &wt) {
				wt.Del(fid)
				p.ls.Put(wk, &wt)
			}
		}
	}
	// 保存文件包含的词
	p.ls.Put(fk, &ts)
	ts.DelTags(ots)
	// 增加词关联文件
	for _, wid := range ts.Slice() {
		wt := tags.NewTags()
		wk := p.wordFilePrefix[:]
		wk = append(wk, u32.Bytes(wid)...)

		_ = p.ls.Get(wk, &wt)
		wt.Add(fid)
		p.ls.Put(wk, &wt)
	}
}

// FilesByStart 索引文件列表.
func (p *FileService) FilesByStart(start string) []*pb.File {
	keys := [][]byte{}
	startKey := p.fileIDKey[:]

	if start != "" {
		startKey = append(startKey, []byte(start)...)
	}

	ran := util.BytesPrefix(p.fileIDKey[:])
	if len(start) > 0 {
		ran.Start = startKey
	}

	iter := p.ls.DB.NewIterator(ran, nil)
	for i := 0; i < int(p.ctx.Limit) && iter.Next(); i++ {
		bs := make([]byte, len(iter.Value()))

		copy(bs, iter.Value())
		keys = append(keys, bs)
	}

	ret := make([]*pb.File, len(keys))

	for i, key := range keys {
		fk := p.fileWordPrefix[:]
		fk = append(fk, key[2:]...)
		ts := tags.NewTags()
		ret[i] = &pb.File{}

		p.ls.Get(key, ret[i])
		// 文件包含的词数量
		if p.ls.Get(fk, &ts) {
			ret[i].WordCount = uint32(ts.Count())
		}
	}

	return ret
}

// FileByWord 根据词ID查找文件信息.
func (p *FileService) FileByWord(ids []uint32) []uint32 {
	logs.Debug("ids:", ids)

	fileTags := []tags.Tags{}
	set := sets.NewUint32s(ids...)

	for wid := range set {
		wt := tags.NewTags()
		wk := []byte{}
		wk = append(wk, p.wordFilePrefix[:]...)
		wk = append(wk, u32.Bytes(wid)...)

		if p.ls.Get(wk, &wt) {
			fileTags = append(fileTags, wt)
		}
	}

	fileTag := tags.Intersection(fileTags...)

	return fileTag.Slice()
}

// Files 根据文件ID返回文件信息.
func (p *FileService) Files(ids []uint32) []*pb.File {
	files := make([]*pb.File, len(ids))

	logs.Debug("file.Count", len(ids))
	logs.Debug("file.Slice", ids)

	for i, fid := range ids {
		if h, f := p.fileByID(fid); h {
			files[i] = f
		}
	}

	return files
}

// 根据文件ID获取pb.File.
func (p *FileService) fileByID(id uint32) (bool, *pb.File) {
	fk := p.fileIDPrefix[:]
	f := &pb.File{}

	fk = append(fk, u32.Bytes(id)...)

	return p.ls.Get(fk, f), f
}
